// This is JDefenceTower, a simple tower defence style game
//
// Copyright (C) 2010 Maximilian Junker
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 3 of the License, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, see <http://www.gnu.org/licenses/>.

package junker.defence.model;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Game {
	private static Game					instance	= null;
	private List<Player>				players		= new LinkedList<Player>();
	private static final Logger			logger		= Logger.getLogger(Game.class
															.getName());
	private List<IGameEventListener>	listeners	= new LinkedList<IGameEventListener>();
	private long						oldLastTime;
	private long						lastTime;
	private long						startTime;

	private Game() {

	}

	/**
	 * Puts a MapObject in a queue to be added for <em>player</em> at the grid
	 * location <em>loc</em>
	 * 
	 * @param player
	 * @param o
	 * @param loc
	 */
	public void addMapObjectToQueue(Player player, MapObject o, MapLocation loc) {
		assert o.getLocation() == null;
		assert o.getMap() == null;

	}

	public void queueEvent(GameRequest e) {

	}

	public void gameLoop() {
		HashMap<Player, StepSummary> summaries = getInitialStepSummaries();
		List<GameRequest> events = null;
		StepSummary s = null;
		int steps = 0;
		long startTime = System.nanoTime();
		long lastTime = 0;
		long oldLastTime = 0;

		for (steps = 0;; steps++) {

			TimeSpan span  = nextFrame();

			logger.log(Level.FINEST, String.format("Step %d", steps));
			for (Player p : players) {
				assert summaries.containsKey(p);

				StepSummary oldSum = summaries.get(p);
				events = collectRequests(p, oldSum, span);
				s = new StepSummary();
				summaries.put(p, s);
				while (events.size() > 0) {
					createStepSummaryFromEventList(s, steps, p, events);
					events = processSummary(s, oldSum, span);
				}
			}
			notifyListeners();
		}
	}

	private TimeSpan nextFrame() {
		// If frame-time was not used, wait
		oldLastTime = lastTime;
		long time = System.nanoTime() - startTime;
		while (time - lastTime < Config.FRAME_TIME) {
			Thread.yield();
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
			time = System.nanoTime() - startTime;
		}
		lastTime = time;
		return new TimeSpan(oldLastTime, time);
	}

	private HashMap<Player, StepSummary> getInitialStepSummaries() {
		HashMap<Player, StepSummary> summaries = new HashMap<Player, StepSummary>();
		for (Player p : players) {
			summaries.put(p, new StepSummary());
		}
		return summaries;
	}

	private StepSummary createStepSummaryFromEventList(StepSummary summary,
			int steps, Player p, List<GameRequest> events) {
		int barriers = 0;
		logger.log(Level.FINEST, "Building Summary");
		for (GameRequest e : events) {
			if (e instanceof CreateRequest) {
				CreateRequest c = (CreateRequest) e;
				if (!p.getMap().isMapLocationBlocked(c.getMapLocation())) {
					if (summary.containsCreationAt(c.getMapLocation())) {
						List<CreateRequest> cons = summary.getCreationsAt(c
								.getMapLocation());
						if (cons.size() == 1
								&& cons.get(0).getMapObjectDescription()
										.isBarrier()) {
							CreateRequest con = cons.get(0);
							if (c.getPriority() > con.getPriority()) {

								summary.addCreation(c.getMapLocation(), c);
								if (c.getMapObjectDescription().isBarrier())
									barriers++;

								summary.addConflictingCreation(con);
								if (con.getMapObjectDescription().isBarrier())
									barriers--;

								logger.log(
										Level.FINEST,
										String.format("Replace %s\n by %s",
												con.toString(), c.toString()));
							} else {
								summary.addConflictingCreation(c);
								logger.log(Level.FINEST, String.format(
										"Conflict: %s\n because of %s",
										c.toString(), con.toString()));
							}
						} else {
							summary.addCreation(c.getMapLocation(), c);
							if (c.getMapObjectDescription().isBarrier())
								barriers++;
							logger.log(Level.FINEST,
									String.format("Adding %s", c.toString()));
						}
					} else {
						summary.addCreation(c.getMapLocation(), c);
						if (c.getMapObjectDescription().isBarrier())
							barriers++;
						logger.log(Level.FINEST,
								String.format("Adding %s", c.toString()));
					}
				} else {
					summary.addConflictingCreation(c);
					logger.log(Level.FINEST,
							String.format("Location Blocked: %s", c.toString()));
				}
			}
			if (e instanceof MoveRequest) {
				MoveRequest m = (MoveRequest) e;
				if (summary.containsMoveFor(m.getMapObject())) {
					MoveRequest con = summary.getMoveFor(m.getMapObject());
					if (m.getPriority() > con.getPriority()) {
						summary.replaceMove(m.getMapObject(), m);
						summary.addConflictingMove(con);
						logger.log(
								Level.FINEST,
								String.format("Replace %s\n by %s",
										con.toString(), m.toString()));
					} else {
						summary.addConflictingMove(m);
						logger.log(
								Level.FINEST,
								String.format("Conflict: %s\n because of %s",
										m.toString(), con.toString()));
					}
				} else
					summary.replaceMove(m.getMapObject(), m);
				logger.log(Level.FINEST,
						String.format("Adding %s", m.toString()));
			}
		}
		if (barriers > 0)
			summary.setBarrierCreated(true);
		return summary;
	}

	private List<GameRequest> processSummary(StepSummary s,
			StepSummary oldSummary, TimeSpan span) {
		List<GameRequest> events = new LinkedList<GameRequest>();
		for (MoveRequest m : s.getValidMoveEvents()) {
			if (m.isHandled())
				continue;
			m.getMapObject().setLocation(m.getMapLocation());
			m.commit();
		}
		for (GameRequest m : s.getConflictingMoveEvent()) {
			if (m.isHandled())
				continue;
			m.conflict();
		}

		for (List<CreateRequest> cs : s.getValidCreateEvents()) {
			for (CreateRequest c : cs) {
				if (c.isHandled())
					continue;
				MapObject o = c.getMapObjectDescription().createMapObject(
						c.getMap());
				c.getMap().addMapObject(o, c.getMapLocation());
				c.commit();
				events.addAll(o.collectRequests(oldSummary,
						new TimeSpan(c.getCreateTime(), span.getEnd())));
			}
		}
		for (GameRequest c : s.getConflictingCreateEvents()) {
			if (c.isHandled())
				continue;
			c.conflict();
		}
		return events;
	}

	private List<GameRequest> collectRequests(Player p, StepSummary s,
			TimeSpan span) {
		logger.log(Level.FINEST, "Collecting events");
		List<GameRequest> events = p.collectRequests(s, span);
		logger.log(Level.FINEST,
				String.format("%d events collected", events.size()));
		return events;
	}

	public List<Player> getPlayers() {
		return players;
	}

	public void setPlayers(List<Player> players) {
		this.players = players;
	}

	public static Game getInstance() {
		if (null == instance)
			instance = new Game();
		return instance;

	}

	public void addGameEventListener(IGameEventListener listener) {
		this.listeners.add(listener);
	}

	private void notifyListeners() {
		for (IGameEventListener l : listeners) {
			l.stepComplete();
		}
	}

}
